package kotlincore.chapt02.高阶函数和Lambda.实例函数作为参数的需求

/*
* Shaw因为旅游喜欢上了地理，然后他建了一个所有国家的数据库。作为一名程序员，
* 他设计了一个CountryApp类对国家数据进行操作。Shaw偏好欧洲的国家，于是他
* 设计了一个程序来获取欧洲的所有国家。
* */

//Country数据类
data class Country(
    val name: String,
    val continient: String,
    val population: Int
)

class CountryApp {
    /**
     * 获取欧洲的所有国家
     */
    fun filterCountries(countries: List<Country>): List<Country> {
        val res = mutableListOf<Country>()
        for (c in countries) {
            if (c.continient == "EU") { //EU代表欧洲
                res.add(c)
            }
        }
        return res
    }

    /*
    后来,Shaw对非洲也产生了兴趣，于是他又改进了上述方法的实例，支持根据具体的洲来筛选国家。
    * */
    fun filterCountries(countries: List<Country>, continient: String): List<Country> {
        val res = mutableListOf<Country>()
        for (c in countries) {
            if (c.continient == continient) {
                res.add(c)
            }
        }
        return res
    }

    /*
    * 以上程序具备了一定的复用性。然而，Shaw的地理知识越来越丰富了，他想对国家的特点
    * 做进一步的研究，比如筛选具有一定人口规模的国家，于是代码以变成下面这个样子
    * */
    fun filterCountries(countries: List<Country>, continient: String, population: Int): List<Country> {
        val res = mutableListOf<Country>()
        for (c in countries) {
            if (c.continient == continient && c.population > population) {
                res.add(c)
            }
        }
        return res
    }

    /*
    * 新增了一个population的参数来代表人口(单位:万)。Shaw开始感觉到不对劲，如果
    * 按照现有的设计，更多的筛选条件作为方法参数而不断累加，而且业务逻辑也高度耦合。
    * 解决问题的核心在于对filterCountries方法进行解耦，我们能否把所有的筛选逻辑行为
    * 都抽象成一个参数呢？传入一个类的对象是一种解决方法，我们可以根据不同的筛选需求创建
    * 不同的子类，它们都各自实现了一个校验方法。然而，Shaw了解到Kotlin是支持高阶函数的，
    * 理论上我们同样可以把筛选逻辑变成一个方法来传入，这种思中更加简单。
    * */

    fun filterCountries(
        countries: List<Country>,
        test: (Country) -> Boolean
    ): List<Country> {
        val res = mutableListOf<Country>()
        for (c in countries) {
            if (test(c)) { //直接调用test来进行筛选
                res.add(c)
            }
        }
        return res
    }

}

class CountryTest {
    fun isBigEuropeanCountry(country: Country): Boolean {
        return country.continient == "欧洲" && country.population > 8000
    }

    /*
    * 调用isBigEuropeanCountry方法就能判断一个国家是否是一个人口超过1亿的欧洲国家。
    * 然而，怎样才能把这个方法变成filterCountries方法的一人参数呢？要实例这一点似乎
    * 要先解决以下丙个问题
    * 1，方法作为参数传入，必须像其他参数一样具备具体的类型信息 。
    * 2，需要把isBigEuropeanCountry的方法引用当作参数传递给filterCountries。
    *
    * */
}

fun main() {
    //Shaw便使用了方法引用来传递参数
    val countryApp = CountryApp()
    val countryTest = CountryTest()
    val countries = listOf<Country>(
        Country("德国", "欧洲", 8312),
        Country("法国", "欧洲", 6524),
    )
    val filtercountry = countryApp.filterCountries(countries, countryTest::isBigEuropeanCountry)
    println(filtercountry)

    //Kotlin支持在缺省函数名的情况下，直接定义一个函数。
    fun(country:Country):Boolean{
        return country.continient == "EU" && country.population > 8000
    }

    //Shaw直接调用filterCountries
    countryApp.filterCountries(countries,fun(country:Country):Boolean{
        return country.continient == "EU" && country.population > 8000
    })

    //Lambda是语法糖
    /*Lambda表达式可以理解为简化表达后的匿名函数，实质上它就是一个语法糖
    我们来分析filterCountries方法的匿名函数，会发现"
    1,fun(country:Country)显得比较啰唆，因为编译器会推导类型，所以只需要一个代表变量country就行了
    2，return关键字可以省略，这里返回一具有值的表达式
    3，模仿函数类型的语法，我们可以用->把参数和返回值连接在一起。
    * 因此，简化后的表达就变成了这个样子:
    * */
    countryApp.filterCountries(countries,{
        country ->
        country.continient == "欧洲" && country.population > 8000
    })
    //是不是非常简洁？这个就是Lambda表达式，它与匿名函数一样， 一种函数字面量。



    countryApp.filterCountries(countries) { country ->
        country.continient == "欧洲" && country.population > 8000
    }

    //使用函数变量
    val filterFunction:(Country)->Boolean ={ country ->
        country.continient == "欧洲" && country.population > 10000
    }
    countryApp.filterCountries(countries,filterFunction)
}
